package com.sharding.test.plugin;

import com.sharding.test.builder.ShardConfigHolder;
import com.sharding.test.builder.ShardConfigParser;
import com.sharding.test.converter.SqlConverterFactory;
import com.sharding.test.util.ReflectionUtils;
import org.apache.ibatis.executor.statement.RoutingStatementHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;

import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;

@Intercepts( {@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
public class ShardPlugin implements Interceptor {

    private final static Log log = LogFactory.getLog(ShardPlugin.class);

    public final String SHARDING_CONFIG = "shardingConfig";

    private final static ConcurrentHashMap<String, Boolean> cache = new ConcurrentHashMap<String, Boolean>();

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();

        MappedStatement mappedStatement = null;
        if (statementHandler instanceof RoutingStatementHandler) {
            StatementHandler delegate = (StatementHandler) ReflectionUtils.getFieldValue((RoutingStatementHandler) statementHandler, "delegate");
            mappedStatement = (MappedStatement) ReflectionUtils.getFieldValue(delegate, "mappedStatement");
        } else {
            mappedStatement = (MappedStatement) ReflectionUtils.getFieldValue(statementHandler, "mappedStatement");
        }

        String mappedId = mappedStatement.getId();

        if (isShouldParse(mappedId)) {
            String sql = statementHandler.getBoundSql().getSql();
            if (log.isDebugEnabled()) {
                log.debug("Original Sql [" + mappedId + "]:" + sql);
            }
            Object params = statementHandler.getBoundSql().getParameterObject();

            SqlConverterFactory converterFactory = SqlConverterFactory.getInstance();
            sql = converterFactory.convert(sql, params, mappedId);

            if (log.isDebugEnabled()) {
                log.debug("Converted Sql [" + mappedId + "]:" + sql);
            }

            ReflectionUtils.setFieldValue(statementHandler.getBoundSql(), "sql", sql);
        }

        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        String config = properties.getProperty(SHARDING_CONFIG, null);

        if (config == null || config.trim().length() == 0) {
            throw new IllegalArgumentException("property 'shardingConfig' is requested");
        }

        ShardConfigParser parser = new ShardConfigParser();
        InputStream inputStream = null;

        try {
            inputStream = Resources.getResourceAsStream(config);
            parser.parse(inputStream);
        } catch (IOException e) {
            log.error("Get sharding config file failed.", e);
            throw new IllegalArgumentException(e);
        } catch (Exception e) {
            log.error("Parse sharding config file failed.", e);
            throw new IllegalArgumentException(e);
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    log.error(e.getMessage(), e);
                }
            }
        }


    }

    private boolean isShouldParse(String mapperId) {
        Boolean parse = cache.get(mapperId);

        if (parse != null) {
            return parse;
        }

        /*
         * 1.<selectKey>不做解析
         * 2.在ignoreList里的sql不用处理
         * 3.如果不在ignoreList里并且没有配置parseList则进行处理
         * 4.如果不在ignoreList里并且也在parseList里则进行处理
         * 5.如果不在ignoreList里并且也不在parseList里则不进行处理
         */
        if (!mapperId.endsWith("!selectKey")) {
            ShardConfigHolder configHolder = ShardConfigHolder.getInstance();
            System.out.println("configHolder.isParseId(mapperId) = " + configHolder.isParseId(mapperId));
            if (!configHolder.isIgnoredId(mapperId) && (!configHolder.isConfigParseId() || configHolder.isParseId(mapperId))) {
                parse = true;
            }
        }

        if (parse == null) {
            parse = false;
        }

        cache.put(mapperId, parse);

        return parse;

    }
}
